/*
Our failed attempt to enumerate fonts in an external asset library:

var classRef : Class = ( event.target as Asset ).loader.contentLoaderInfo.applicationDomain.getDefinition ( "flash.text.Font" ) as Class;
var fonts:Array = classRef [ "enumerateFonts" ] ( );
trace ( "fonts",fonts.length);
for ( var i : int = 0; i < fonts.length; i++ ) trace ( fonts[i].fontName,fonts[i].fontStyle,fonts[i].fontType );

*/

package com.frontalcode
{
	import flash.text.*;
	
    public class FontManager extends OptionableObject
    {
		// The font map array is comprised of objects with the keys filter 
		// (either a string or a RegExp) and name (a string).
		//
		private var fontMap : Array = [ ];
		
		// The fonts hash tracks all the font names we know about.
		//
		private var fonts : Object = { };
		
		// The listeners object tracks those calls to resolveFont that failed 
		// but that want an onLoad callback when the font is registered.
		//
		// The key of the object is the font name of interest. Each value is an 
		// Array of objects with keys name (the original font name requested) 
		// and onLoad functions.
		//
		private var listeners : Object = { };
		
		public function FontManager ( options : Object = null )
		{
			super ( options );
			
			for each ( var font : Font in Font.enumerateFonts ( true ) ) 
			{
				if ( fonts [ font.fontName ] == null || font.fontType == FontType.EMBEDDED ) fonts [ font.fontName ] = font;
			}
		}
		
		/*
			Function: resolveFont
			
				This function looks for a matching font for the given name. If
				the FontManager cannot find a match or if it can only find a 
				device font, then it will save the optional onLoad closure to
				be called if and when a better version of the font is 
				registered. (A "better" version is a device font over nothing 
				and an embedded font over a device font.)
				
			Parameters:
			
				name	- The name of the font to resolve.
				onLoad	- An optional callback.
				
						  internal function onLoad ( fontName : String, resolvedFont : Font ) : void
						  
			Returns:
			
				An Object with keys name ((the resolved font name or null for 
				the system default font) and isEmbedded.
		*/
		internal function resolveFont ( name : String, onLoad : Function = null ) : Object
		{
			var mapName : String = getMappedName ( name );
			
			var font : Font = fonts [ mapName ];
			
			if ( onLoad != null && ( font == null || font.fontType == FontType.DEVICE ) )
			{
				if ( listeners [ mapName ] == null ) listeners [ mapName ] = [ ];
				
				if ( findListener ( mapName, onLoad ) < 0 )
				{
					listeners [ mapName ].push ( { name: name, onLoad: onLoad } );
				}
			}
			
			return { name: font == null ? null : mapName, isEmbedded: font == null ? false : font.fontType == FontType.EMBEDDED };
		}
		
		/*
			Function: registerFont
			
				This function adds a font to our list of known fonts. It also
				dispatches an event if the font is new or is embedded and over-
				writes a device font.
				
			Parameters:
			
				font	- The font to register.
		*/
		internal function registerFont ( fontClass : Class ) : void
		{
			var result : * = new fontClass ( );
			
			if ( result is Font )
			{
				Font.registerFont ( fontClass );
			
				var font : Font = result as Font;
			
				if ( fonts [ font.fontName ] == null || ( font.fontType == FontType.EMBEDDED && fonts [ font.fontName ].fontType == FontType.DEVICE ) ) 
				{
					fonts [ font.fontName ] = font;
					
					if ( listeners [ font.fontName ] != null )
					{
						for ( var i : int = 0; i < listeners [ font.fontName ].length; i++ )
						{
							var listener : Object = listeners [ font.fontName ] [ i ];
							if ( font.fontType == FontType.EMBEDDED ) listeners [ font.fontName ].splice ( i--, 1 );
							listener.onLoad ( listener.name, font );
						}
						
						if ( listeners [ font.fontName ].length == 0 ) delete listeners [ font.fontName ];
					}
				}
			}
		}
		
		/*
			Function: getMappedName
			
				This function gets the mapped font name for the given name.
				We pass the name through our font map. The font map works by 
				passing the font name through an array of entries. Once a 
				matching entry is found, the mapping is considered complete and 
				no more entries are examined.
				
			Parameters:
			
				name	- The font name to get the mapping for.
				
			Returns:
			
				The mapped name. If no mapping exists, the name passed in is
				returned.
		*/
		internal function getMappedName ( name : String ) : String
		{
			for ( var i : uint = 0; i < fontMap.length; i++ )
			{
				var filter : * = fontMap [ i ].filter;
				if (
						( filter is RegExp && filter.test ( name ) ) ||
						( filter == name )
					)
				{
					name = fontMap [ i ].name;
					break;
				}
			}
			
			return name;
		}
		
		/*
			Function: addMapping
			
				Add the given mapping to the fontMap. The font map is an ordered
				array and matches are attempted from the first to the last. Once
				a match is found, no further matches are attempted.
				
			Parameters:
			
				filter			- A String or RegExp to match against requested font names.
				name			- The mapped name.
				toBeginning		- Add the filter to the front of the map array.
				
			Notes:
			
				XXX The array is not checked for duplicate entries. 
		*/
		internal function addMapping ( filter : *, name : String, toBeginning : Boolean = false ) : void
		{
			if ( toBeginning )
			{
				fontMap.unshift ( { filter: filter, name: name } );
			}
			else
			{
				fontMap.push ( { filter: filter, name: name } );
			}
		}
		
		/*
			Function: removeOnLoadCallback
			
				Use this function to remove an onLoad callback added by calling
				resolveFont.
				
			Parameters:
			
				name				- The font being resolved.
				onLoad				- The callback closure.
		*/
		internal function removeOnLoadCallback ( name : String, onLoad : Function ) : void
		{
			var index : int = findListener ( name, onLoad );
			if ( index >= 0 ) listeners [ name ].splice ( index, 1 );
		}
		
		private function findListener ( name : String, onLoad : Function = null ) : int
		{
			var result : int = -1;
			
			if ( listeners [ name ] == null ) return result;
			
			for ( var i : uint = 0; i < listeners [ name ].length; i++ )
			{
				if ( listeners [ name ] [ i ].onLoad === onLoad )
				{
					result = i;
					break;
				}
			}
			
			return result;
		}
    }
}

